package org.springframework.extcache.multilevel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.core.annotation.Order;
import org.springframework.extcache.ConfigurableCache;
import org.springframework.extcache.FusibleCacheManager;

/**
 * 多层级缓存管理器，缓存被划分为初级和末级，初级的缓存执行顺序高于末级。<br>
 * <br>
 * 使用案例：<br>
 * 1、配置多级缓存，cacheManager类中的@Order配置级别
 * 
 * <p>
 * <bean id="multiLevelCacheManager" class=
 * "org.springframework.extcache.multilevel.MultiLevelCacheManager">
 * <property name="cacheManagers"> <list>
 * <ref bean="concurrentMapCacheManager" /> <ref bean="redisCacheManager" />
 * </list> </property> <property name="isSuperiorCheck" value="true" /> </bean>
 * </p>
 * 2、在spring
 * 4.3.x后，可以在注解中使用cacheManager属性，没有有指定且向spring托管了<cache:annotation-driven
 * cache-manager= "multiLevelCacheManager" />，表明开启多级缓存<br>
 * 
 * @Cacheable(value="userCache", key="userId")
 * <p>
 * </p>
 * <p>
 * userService.get(String userId);
 * </p>
 * 
 * @version 1.0.0
 * @author zhangz8@yeah.net
 * @date 2019年12月30日上午9:37:42
 */
public class MultiLevelCacheManager implements CacheManager {

  private final List<CacheManager> cacheManagers   = new ArrayList<CacheManager>();
  private MultiLevelNavigator      navigator;
  private boolean                  isSuperiorCheck = false;

  @Override
  public Cache getCache(String name) {
    CacheManager cacheManager = navigator.getFirst();
    Cache cache = cacheManager.getCache(name);
    MultiLevelCache multiLevelCache = new MultiLevelCache((ConfigurableCache) cache, isSuperiorCheck, isPutable());
    buildChain(multiLevelCache);
    return multiLevelCache;
  }

  /**
   * 构建多级缓存链
   * 
   * @author zhangz8@yeah.net
   * @date 2020年1月1日下午9:37:17
   * @param multiLevelCache
   */
  private void buildChain(MultiLevelCache multiLevelCache) {
    CacheManager cacheManager = multiLevelCache.getCacheManager();
    CacheManager nextCacheManager = navigator.getNext(cacheManager);
    if (nextCacheManager == null) {
      return;
    }

    Cache next = nextCacheManager.getCache(multiLevelCache.getName());
    MultiLevelCache nextMultiLevelCache = new MultiLevelCache((ConfigurableCache) next, isSuperiorCheck, isPutable());
    multiLevelCache.setNext(nextMultiLevelCache);
    multiLevelCache.setSuperiorCacheManager(nextCacheManager);
    nextMultiLevelCache.setPrevious(multiLevelCache);

    buildChain(nextMultiLevelCache);
  }

  /**
   * 末节点cach容器不可用，不允许链路上的所有容器放数据。
   * 
   * @author zhangz8@yeah.net
   * @return
   */
  public boolean isPutable() {
    CacheManager cacheManager = navigator.getLast();
    if (cacheManager instanceof FusibleCacheManager) {
      FusibleCacheManager fusibleCacheManager = (FusibleCacheManager) cacheManager;
      return !fusibleCacheManager.isFused();
    }
    return true;
  }

  public void setCacheManagers(Collection<CacheManager> cacheManagers) {
    this.cacheManagers.addAll(cacheManagers);
    this.navigator = new MultiLevelNavigator(this.cacheManagers);
  }

  class MultiLevelNavigator {
    LinkedList<CacheManager> orderedCacheManagers;

    MultiLevelNavigator(List<CacheManager> cacheManagers) {
      if (orderedCacheManagers == null) {
        LinkedList<CacheManager> managers = new LinkedList<CacheManager>(cacheManagers);
        Collections.sort(managers, new Comparator<CacheManager>() {
          @Override
          public int compare(CacheManager o1, CacheManager o2) {
            int order1 = o1.getClass().getAnnotation(Order.class).value();
            int order2 = o2.getClass().getAnnotation(Order.class).value();
            if (order1 > order2) {
              return 1;
            }
            return -1;
          }
        });
        this.orderedCacheManagers = managers;
      }
    }

    CacheManager getLast() {
      return orderedCacheManagers.getLast();
    }

    CacheManager getNext(CacheManager cacheManager) {
      if (cacheManager == null) {
        return orderedCacheManagers.get(0);
      }
      int index = getIndex(cacheManager) + 1;
      if (index >= 0 && index <= orderedCacheManagers.size() - 1) {
        return orderedCacheManagers.get(index);
      }
      return null;
    }

    CacheManager getPrevious(CacheManager cacheManager) {
      int index = getIndex(cacheManager) - 1;
      if (index >= 0 && index <= orderedCacheManagers.size() - 1) {
        return orderedCacheManagers.get(index);
      }
      return null;
    }

    CacheManager getFirst() {
      return orderedCacheManagers.getFirst();
    }

    int getIndex(CacheManager cacheManager) {
      for (int i = 0; i < orderedCacheManagers.size(); i++) {
        if (orderedCacheManagers.get(i).equals(cacheManager)) {
          return i;
        }
      }
      return -1;
    }
  }

  public void setIsSuperiorCheck(boolean isSuperiorCheck) {
    this.isSuperiorCheck = isSuperiorCheck;
  }

  @Override
  public Collection<String> getCacheNames() {
    Set<String> names = new LinkedHashSet<String>();
    for (CacheManager manager : this.cacheManagers) {
      names.addAll(manager.getCacheNames());
    }
    return Collections.unmodifiableSet(names);
  }

}
